package cn.springcloud.fix.gateway.service.impl;

import cn.springcloud.fix.common.core.utils.StringUtil;
import cn.springcloud.fix.gateway.dao.entity.GatewayRoute;
import cn.springcloud.fix.gateway.dao.mapper.GatewayRouteMapper;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * https://blog.csdn.net/weixin_37490221/article/details/78135036(Redis Hash用法)
 * https://www.jianshu.com/u/74a89e9b4fbe (gateway二次开发)
 */
@Slf4j
@Component
@AllArgsConstructor
public class RouteDefinitionInit implements RouteDefinitionRepository {

    private static final String GATEWAY_ROUTE_KEY = "gateway_route_key";

    private GatewayRouteMapper gatewayRouteMapper;

    /**
     * @see org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
     */
    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(loadRouteConfig());
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            //先保存到数据库再保存到redis
            redisTemplate.opsForHash().put(GATEWAY_ROUTE_KEY, routeDefinition.getId(), JSON.toJSONString(routeDefinition));
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            //先删除数据库再删除redis
            if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTE_KEY, id)) {
                redisTemplate.opsForHash().delete(GATEWAY_ROUTE_KEY, id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }

    /**
     * 从数据库加载到redis缓存里
     */
    private List<RouteDefinition> loadRouteConfig() {
        List<RouteDefinition> routeDefinitions = this.getRouteDefinitionBOsFromRedis();
        if (CollectionUtils.isNotEmpty(routeDefinitions)) {
            return routeDefinitions;
        }

        //缓存没有就去数据库加载
        List<GatewayRoute> definitionList = gatewayRouteMapper.selectList(Wrappers.<GatewayRoute>lambdaQuery().eq(GatewayRoute::getStatus, true));
        if (CollectionUtils.isEmpty(definitionList)) {
            return new ArrayList<>();
        }

        routeDefinitions = new ArrayList<>();
        Map<String, String> param = new HashMap<>();
        for (GatewayRoute gatewayRoute : definitionList) {
            RouteDefinition routeDefinition = this.assembleRouteDefinition(gatewayRoute);
            param.put(routeDefinition.getId(), JSON.toJSONString(routeDefinition));
            routeDefinitions.add(routeDefinition);
        }
        redisTemplate.opsForHash().putAll(GATEWAY_ROUTE_KEY, param);
        return routeDefinitions;
    }

    private List<RouteDefinition> getRouteDefinitionBOsFromRedis() {
        List<RouteDefinition> routeDefinitions = new ArrayList<>();
        redisTemplate.opsForHash().values(GATEWAY_ROUTE_KEY).forEach(e -> routeDefinitions.add(JSON.parseObject((String) e, RouteDefinition.class)));
        return routeDefinitions;
    }

    private RouteDefinition assembleRouteDefinition(GatewayRoute gatewayRoute) {
        RouteDefinition routeDefinition = new RouteDefinition();
        routeDefinition.setId(gatewayRoute.getRouteId());
        routeDefinition.setOrder(gatewayRoute.getRouteOrder());
        routeDefinition.setUri(URI.create(gatewayRoute.getRouteUri()));
        //获取过滤器集合
        if (StringUtil.isNotEmpty(gatewayRoute.getFilters())) {
            List<FilterDefinition> definitions = JSON.parseArray(gatewayRoute.getFilters(), FilterDefinition.class);
            routeDefinition.setFilters(definitions);
        }
        //获取断言集合
        if (StringUtil.isNotEmpty(gatewayRoute.getPredicates())) {
            List<PredicateDefinition> definitions = JSON.parseArray(gatewayRoute.getPredicates(), PredicateDefinition.class);
            routeDefinition.setPredicates(definitions);
        }
        return routeDefinition;
    }
}
